package com.heaboy.springioc.ioc;

import com.heaboy.springioc.stereotype.Autowired;
import com.heaboy.springioc.stereotype.Component;

import java.io.File;
import java.io.FileNotFoundException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.*;

public class SpringIOC {

    private List<String> beanNames;
    private List<String> filePaths;
    private String basePath;
    private String basePackage;

    private Map<String, Object> beans =new HashMap<>();
    /**
     * 扫描所有的文件信息信息，存到了 filePaths
     */
    private void scan() throws FileNotFoundException {
        File file = new File(basePath);
        filePaths = new ArrayList<>();
        if(file.exists()){
            Queue<File> queue = new LinkedList<>();
            queue.add(file);
            while(!queue.isEmpty()){
                File poll = queue.poll();
                if(poll == null){
                    continue;
                }
                if(poll.isDirectory()){//如果是文件夹
                    File[] files = poll.listFiles();
                    for (File f : files) {
                        queue.add(f);
                    }
                }else {
                    filePaths.add(poll.getPath());
                }
            }
        }else {
            throw new FileNotFoundException(basePath+" not found");
        }
    }

    /**
     * 将所有的.java结尾的 全限定名放到 beanNames
     */
    public void  initBeanNames(){
        for (String s : filePaths) {//循环遍历文件信息
            String replace = s.replace(basePath, "");//替换路径
            if(replace.endsWith(".java")) {
                replace = replace.substring(0, replace.length()-5);//去掉.java后缀
            }

            char[] chars = replace.toCharArray();//切换数组
            for (int i = 0; i < chars.length; i++) {
                if(chars[i]=='\\'){
                    chars[i] = '.';
                }
            }
            beanNames.add(basePackage+"."+new String(chars));
        }
    }

    /**
     *这个方法实现了简单的依赖注入功能。
     * 它首先加载并实例化所有被 @Component 注解标记的类，并将它们存储在一个 Map 中。
     * 然后，对于每个 bean（类） 的每个声明字段，如果字段被 @Autowired 注解标记，则从 Map 中查找对应的依赖并注入到字段中。
     * 这个过程模拟了Spring框架中的部分功能：IOC依赖注入。
     */
    public void initBeans(){
        for (String beanName : beanNames) {//遍历：所有的.java结尾的 全限定名
            try {
                Class<?> aClass = Class.forName(beanName);//加载类
                Annotation[] declaredAnnotations = aClass.getDeclaredAnnotations();//获取类上声明的注解
                for (Annotation declaredAnnotation : declaredAnnotations) {
                    //如果有@Component，那么创建该类的一个实例，并将其以类的全限定名为键，实例对象为值，存入一个名为 beans 的 Map 中。
                    if(declaredAnnotation instanceof Component){
                        Object o = aClass.newInstance();
                        beans.put(aClass.getName(),o);
                    }
                }
            } catch (ClassNotFoundException | InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }

        }

        for (Map.Entry<String, Object> entry : beans.entrySet()) {//遍历 beans 集合中的每个对象
            Field[] declaredFields = entry.getValue().getClass().getDeclaredFields();//获取对象类所有声明的字段。
            //getDeclaredFields()：获得某个类的所有声明的字段，即包括public、private 和proteced，但是不包括父类的申明字段。

            for (Field field : declaredFields) {//遍历每个声明字段

                Annotation[] declaredAnnotations = field.getDeclaredAnnotations();
                //对于每个字段，检查是否有 @Autowired 注解。
                for (Annotation annotation : declaredAnnotations) {
                    if(annotation instanceof Autowired ){
                        //field 需要由我们来赋值
                        // 我们所持有的所有对象 在beans中
                        // 根据当前域中的类型的名字
                        String name = field.getType().getName();//getType() 方法返回 Field 对象表示的字段（声明字段）的 Class 对象（全限定名）。
                        // 从beans 中获得对应的对象
                        Object o = beans.get(name);
                        field.setAccessible(true);//设置可访问
                        try {
                            field.set(entry.getValue(), o);//将该对象（依赖）注入到字段中
                        } catch (IllegalAccessException e) {
                            e.printStackTrace();
                        }
                    }
                }

            }


        }
    }

    /**
     * 通过类名，直接从beans获取类
     * @param beanName
     * @return
     */
    public Object getInstance(String beanName) {
        return beans.get(beanName);
    }
    private void initPath(){
        basePath="D:\\javacode\\ioc\\src\\main\\java\\com\\heaboy\\springioc\\";
        basePackage="com.heaboy.springioc";
    }

    public SpringIOC() {
        initPath();
        try {
            scan();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        beanNames= new ArrayList<>();
        initBeanNames();
    }
}
